/*
 * FileName: GCMonitor.java
 * Author:   zzw
 * Date:     2018年08月21日
 * Description:
 */
package com.zzw.gc;

import com.zzw.utils.ScheduledUtil;
import com.zzw.utils.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.management.*;
import java.lang.management.*;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 〈〉<br>
 * 〈〉
 *
 * @author zzw
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本]（可选）
 */
public class GCMonitor {

    private static final boolean isSunOsBean = checkIsSunOsMBean();
    private static final boolean isUnixSunOsBean = checkIsUnixSunOsBean();
    private static ThreadMXBean threadMXBean = Utils.getThreadMXBean();
    private static Logger logger = LoggerFactory.getLogger(GCMonitor.class);

    public static void main(String[] args) throws InterruptedException {
        GCMonitor monitor = new GCMonitor();
        GCThread gc = monitor.new GCThread();
        GCMemoryThread gcMemoryThread = monitor.new GCMemoryThread();
        ScheduledUtil.addTask(gc);
        ScheduledUtil.addTask(gcMemoryThread);
        ScheduledUtil.addTask(monitor.new GCClassLoadThread());
        ScheduledUtil.addTask(monitor.new RuntimeInfoThread());
        ScheduledUtil.addTask(monitor.new ThreadInfo());
        ScheduledUtil.addTask(monitor.new OSThread());
        Thread.sleep(1000);
    }

    public class GCThread extends Thread{
        @Override
        public void run() {
            Map<String, long[]> lastGcStatusMap = new HashMap<String, long[]>();
            List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
            for (GarbageCollectorMXBean gcBean : gcMXBeans) {
                if (gcBean == null) {
                    continue;
                }
                String name = gcBean.getName();
                long[] vals = lastGcStatusMap.get(name);
                if (vals == null) {
                    vals = new long[]{0, 0};
                    lastGcStatusMap.put(name, vals);
                }

                long currentCollectionCount = gcBean.getCollectionCount();
                long recentCollectionCount = currentCollectionCount - vals[0];
                vals[0] = currentCollectionCount;

                long currentCollectionTime = gcBean.getCollectionTime();
                long recentCollectionTime = currentCollectionTime - vals[1];
                vals[1] = currentCollectionTime;
                logger.info("jvm collect {},{},{},{},{}",name,recentCollectionCount,currentCollectionCount,recentCollectionTime,currentCollectionTime);
            }
        }
    }

    public class GCMemoryThread extends Thread{
        @Override
        public void run() {
            List<MemoryPoolMXBean> memoryPools = ManagementFactory.getMemoryPoolMXBeans();
            for (MemoryPoolMXBean memoryPool : memoryPools) {
                if (memoryPool == null) {
                    continue;
                }
                MemoryUsage usage = memoryPool.getUsage();
                if (usage == null) {
                    continue;
                }
                String poolName = memoryPool.getName();
                logger.info("gc memoey {},{},{},{},{}", poolName,usage.getInit(), usage.getUsed(), usage.getCommitted(), usage.getMax());
            }
        }
    }

    public class GCClassLoadThread extends Thread{

        private long lastTotalLoadedClassCount = 0;
        private long lastTotalUnloadedClassCount = 0;

        @Override
        public void run() {
            ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();

            long currentTotalLoadedClassCount = classLoadingMXBean.getTotalLoadedClassCount();
            long recentLoadedClassCount = currentTotalLoadedClassCount - lastTotalLoadedClassCount;
            lastTotalLoadedClassCount = currentTotalLoadedClassCount;

            long currentTotalUnloadedClassCount = classLoadingMXBean.getUnloadedClassCount();
            long recentUnloadedClassCount = currentTotalUnloadedClassCount - lastTotalUnloadedClassCount;
            lastTotalUnloadedClassCount = currentTotalUnloadedClassCount;

            logger.info("class load {},{},{},{},{}",recentLoadedClassCount, currentTotalLoadedClassCount,
                    classLoadingMXBean.getLoadedClassCount(),recentUnloadedClassCount, currentTotalUnloadedClassCount);
        }
    }

    public class OSThread extends Thread{

        @Override
        public void run() {
            OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
            if (isSunOsBean) {
                logger.info("os {},{},{},{},{},{},{},{}",osMXBean.getAvailableProcessors(), (long) (osMXBean.getSystemLoadAverage() * 1000),
                                getLong(osMXBean, "getTotalPhysicalMemorySize"), getLong(osMXBean, "getTotalSwapSpaceSize"),
                                getLong(osMXBean, "getFreePhysicalMemorySize"), getLong(osMXBean, "getFreeSwapSpaceSize"),
                                getLong(osMXBean, "getCommittedVirtualMemorySize"), getLong(osMXBean, "getProcessCpuTime"));
            } else if (osMXBean != null) {
                logger.info("os {},{},{},{},{},{},{},{}",(long) osMXBean.getAvailableProcessors(), (long) (osMXBean.getSystemLoadAverage() * 1000),
                                0, 0, 0, 0, 0, 0);
            }

            if (isUnixSunOsBean) {
                logger.info("os {},{},",getLong(osMXBean, "getOpenFileDescriptorCount"), getLong(osMXBean, "getMaxFileDescriptorCount"));
            } else if (osMXBean != null) {
                logger.info("os {},{},{},{},{}",Utils.getCurrrentPidString(), "os", "fd",0, 0);
            }
        }
    }

    public class RuntimeInfoThread extends Thread{
        @Override
        public void run() {
            RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
            logger.info("{},{},{},{},{}",Utils.getCurrrentPidString(), "runtime", "name",runtimeMxBean.getName());
            logger.info("{},{},{},{}",Utils.getCurrrentPidString(), "runtime", "startTime",String.valueOf(runtimeMxBean.getStartTime()));

            List arguments = runtimeMxBean.getInputArguments();
            if (arguments != null && !arguments.isEmpty()) {
                logger.info("{},{},{},{}",Utils.getCurrrentPidString(), "runtime", "inputArguments",(String[])arguments.toArray(new String[arguments.size()]));
            }

            Map<String, String> systemProperties = runtimeMxBean.getSystemProperties();
            for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
                logger.info("{},{},{},{}",Utils.getCurrrentPidString(),"system", entry.getKey(),entry.getValue());
            }
        }
    }

    public class ThreadInfo extends Thread{
        @Override
        public void run() {
            if (threadMXBean == null) {
                return;
            }
            logThreadCount();
            logThreadDetail();
        }
    }

    private static boolean checkIsSunOsMBean() {
        try {
            // 在非 SUN 的 JVM 中，com.sun.management.OperatingSystemMXBean 无法找到，需要使用反射
            OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
            String className = osMXBean.getClass().getName();
            if ("com.sun.management.OperatingSystem".equals(className) || "com.sun.management.UnixOperatingSystem".equals(className)) {
                return getLong(osMXBean, "getTotalPhysicalMemorySize") > 0L;
            }
        } catch (Throwable t) {
        }

        return false;
    }

    private static boolean checkIsUnixSunOsBean() {
        try {
            // 在非 SUN 的 JVM 中，com.sun.management.UnixOperatingSystemMXBean 无法找到，需要使用反射
            OperatingSystemMXBean osMXBean = ManagementFactory.getOperatingSystemMXBean();
            String className = osMXBean.getClass().getName();
            if ("com.sun.management.UnixOperatingSystem".equals(className)) {
                return getLong(osMXBean, "getOpenFileDescriptorCount") >= 0L;
            }
        } catch (Throwable t) {
        }

        return false;
    }

    private static long getLong(OperatingSystemMXBean operatingSystem, String methodName) {
        try {
            Method e = operatingSystem.getClass().getMethod(methodName, (Class[]) null);
            e.setAccessible(true);
            return ((Long) e.invoke(operatingSystem, (Object[]) null)).longValue();
        } catch (Exception e) {
            return 0L;
        }
    }

    private long lastTotalStartedThreadCount = 0;

    private void logThreadCount() {
        long currentTotalStartedThreadCount = threadMXBean.getTotalStartedThreadCount();
        long recentStartedThreadCount = currentTotalStartedThreadCount - lastTotalStartedThreadCount;
        lastTotalStartedThreadCount = currentTotalStartedThreadCount;
        logger.info(Utils.getCurrrentPidString(), "thread", "count",threadMXBean.getThreadCount(), threadMXBean.getDaemonThreadCount(),
                        recentStartedThreadCount, currentTotalStartedThreadCount);
    }

    private boolean useThreadGroup = true;

    private void logThreadDetail() {
        if (useThreadGroup) {
            try {
                logThreadDetailByThreadGroup();
                return;
            } catch (Throwable e) {
                useThreadGroup = false;
                logger.error("[WARN] fail to logThreadDetailByThreadGroup, use ThreadMxBean instead", e);
            }
        }
        logThreadDetailByThreadMxBean();
    }

    private void logThreadDetailByThreadGroup() {
        ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
        ThreadGroup parentGroup;
        while ((parentGroup = rootGroup.getParent()) != null) {
            rootGroup = parentGroup;
        }

        Thread[] threads = new Thread[rootGroup.activeCount() * 2];
        while (rootGroup.enumerate(threads, true) == threads.length) {
            threads = new Thread[threads.length * 2];
        }
        // 数组尾部可能会包含null

        countThreadStates(threads);
        logThreadInfo(threads);
    }

    private void countThreadStates(Thread[] threads) {
        int newCount = 0;
        int runnableCount = 0;
        int blockedCount = 0;
        int waitingCount = 0;
        int timedWaitingCount = 0;
        int terminatedCount = 0;
        for (Thread thread : threads) {
            if (thread == null) {
                break;
            }

            Thread.State state = thread.getState();
            switch (state) {
                case NEW:
                    ++newCount;
                    break;
                case RUNNABLE:
                    ++runnableCount;
                    break;
                case BLOCKED:
                    ++blockedCount;
                    break;
                case WAITING:
                    ++waitingCount;
                    break;
                case TIMED_WAITING:
                    ++timedWaitingCount;
                    break;
                case TERMINATED:
                    ++terminatedCount;
                    break;
            }
        }

        logger.info("{},{},{},{},{},{},{},{},{},{}",Utils.getCurrrentPidString(), "thread", "state",newCount, runnableCount, blockedCount, waitingCount, timedWaitingCount, terminatedCount);
    }

    private void logThreadInfo(Thread[] threads) {
        boolean enabledCpuTime = threadMXBean.isThreadCpuTimeSupported() && threadMXBean.isThreadCpuTimeEnabled();
        for (Thread thread : threads) {
            if (thread == null) {
                break;
            }

            String threadName = thread.getName();
            if (threadName != null) {
                // 过滤掉和 StatLogger 分隔符不统一的字符
                threadName = threadName.replace('|', '_');
                threadName = threadName.replace(',', ';');
            } else {
                threadName = "null";
            }

            long tid = thread.getId();
            long threadCpuTime, threadUserTime;
            if (enabledCpuTime) {
                threadCpuTime = threadMXBean.getThreadCpuTime(tid);
                threadUserTime = threadMXBean.getThreadUserTime(tid);
            } else {
                threadCpuTime = 0;
                threadUserTime = 0;
            }

            String threadState = thread.getState().toString();
            int daemon = thread.isDaemon() ? 1 : 0;
            logger.info("{},{},{},{},{},{},{}",Utils.getCurrrentPidString(), threadName, threadState,tid, daemon, threadCpuTime, threadUserTime);
        }

    }

    private void logThreadDetailByThreadMxBean() {
        // 数据中间可能会包含 null
        java.lang.management.ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds());

        countThreadStates(threadInfos);
        logThreadInfo(threadInfos);
    }

    private void countThreadStates(java.lang.management.ThreadInfo[] threadInfos) {
        int newCount = 0;
        int runnableCount = 0;
        int blockedCount = 0;
        int waitingCount = 0;
        int timedWaitingCount = 0;
        int terminatedCount = 0;
        for (java.lang.management.ThreadInfo info : threadInfos) {
            if (info == null) {
                continue;
            }
            Thread.State state = info.getThreadState();
            switch (state) {
                case NEW:
                    ++newCount;
                    break;
                case RUNNABLE:
                    ++runnableCount;
                    break;
                case BLOCKED:
                    ++blockedCount;
                    break;
                case WAITING:
                    ++waitingCount;
                    break;
                case TIMED_WAITING:
                    ++timedWaitingCount;
                    break;
                case TERMINATED:
                    ++terminatedCount;
                    break;
            }
        }
        logger.info("{},{},{},{},{},{},{},{},{}",Utils.getCurrrentPidString(), "thread", "state",newCount, runnableCount, blockedCount, waitingCount, timedWaitingCount, terminatedCount);
    }

    private void logThreadInfo(java.lang.management.ThreadInfo[] threadInfos) {
        boolean enabledCpuTime = threadMXBean.isThreadCpuTimeSupported() && threadMXBean.isThreadCpuTimeEnabled();
        for (java.lang.management.ThreadInfo info : threadInfos) {
            if (info == null) {
                continue;
            }
            String threadName = info.getThreadName();
            if (threadName != null) {
                // 过滤掉和 StatLogger 分隔符不统一的字符
                threadName = threadName.replace('|', '_');
                threadName = threadName.replace(',', ';');
            } else {
                threadName = "null";
            }

            long tid = info.getThreadId();
            long threadCpuTime;
            long threadUserTime;
            if (enabledCpuTime) {
                threadCpuTime = threadMXBean.getThreadCpuTime(tid);
                threadUserTime = threadMXBean.getThreadUserTime(tid);
            } else {
                threadCpuTime = 0;
                threadUserTime = 0;
            }

            String threadState = info.getThreadState().toString();
            byte daemon = -1;  // 取不到

            logger.info("{},{},{},{},{},{},{}",Utils.getCurrrentPidString(), threadName, threadState,tid, daemon, threadCpuTime, threadUserTime);
        }
    }

    public void getYoungAndFullGC() throws MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException {
        MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
        ObjectName youngMBean = new ObjectName("java.lang:type=GarbageCollector,name=PS MarkSweep");
        ObjectName tenuredMBean = new ObjectName("java.lang:type=GarbageCollector,name=PS Scavenge");
        System.out.println("YGC:" + mbs.getAttribute(youngMBean, "CollectionCount"));
        System.out.println("FGC:" + mbs.getAttribute(tenuredMBean, "CollectionCount"));
        System.out.println("YGC:" + mbs.getAttribute(youngMBean, "CollectionCount"));
        System.out.println("FGC:" + mbs.getAttribute(tenuredMBean, "CollectionCount"));
    }
}
